import { EventEmitter } from 'events';
import { window } from 'vscode';
import { DebugRuntimeEvent } from './constants';
import { DebugMessage, DebugService } from './debug-mq';
import { global } from './global';
import {
  IRuntimeBreakpoint,
  IRuntimeStack,
  IRuntimeStackFrame,
  IRuntimeStepInTargets,
} from './interface';
import { RuntimeVariable } from './runtime-variable';
import { File } from './virtual-workspaces';

type StackFile = { name: string; type: string; value: string };

export class CloudDebugRuntime extends EventEmitter {
  private _sourceFile?: File;

  /**
   * 当前正在调试的文件
   *
   * @author chitanda
   * @date 2022-11-30 16:11:47
   * @private
   * @type {string}
   */
  get sourceFile() {
    return this._sourceFile;
  }

  /**
   * 当前 debug 变量
   *
   * @author chitanda
   * @date 2022-11-30 16:11:07
   * @private
   */
  private variables = new Map<string, RuntimeVariable>();

  /**
   * 全局 debug 变量
   *
   * @author chitanda
   * @date 2022-11-30 18:11:58
   * @private
   */
  private globalVariables = new Map<string, RuntimeVariable>();

  /**
   * 要执行的行
   *
   * @author chitanda
   * @date 2022-11-30 15:11:49
   * @private
   */
  private _currentLine = 0;

  private get currentLine() {
    return this._currentLine;
  }

  /**
   * 缓存所有文件的断点信息
   *
   * @author chitanda
   * @date 2022-11-30 15:11:45
   * @private
   */
  private breakPoints = new Map<string, IRuntimeBreakpoint[]>();

  // since we want to send breakpoint events, we will assign an id to every event
  // so that the frontend can match events with breakpoints.
  private breakpointId = 1;

  protected ds: DebugService = new DebugService();

  constructor() {
    super();
    this.topicHandle = this.topicHandle.bind(this);
  }

  protected topicHandle(str: string): void {
    const response = new DebugMessage(str);
    if (response.type === 'EVENT') {
      if (response.subtype === 'BREAKPOINTEVENT') {
        this.emit(DebugRuntimeEvent.BREAKPOINT);
        this.breakpointEventHandle(response);
      } else if (response.subtype === 'STEPEVENT') {
        this.emit(DebugRuntimeEvent.STEP);
        this.stepEventHandle(response);
      }
    }
  }

  /**
   * 处理远程调试器触发断点事件
   *
   * @author chitanda
   * @date 2022-11-30 18:11:38
   * @protected
   * @param {DebugMessage} response
   */
  protected breakpointEventHandle(response: DebugMessage): void {
    const { data } = response;
    const { location } = data;
    this._currentLine = location.lineNumber - 1;
    this.setGlobalVariables(data.fields);
    this.setVariables(data.variables);
  }

  /**
   * 处理远程调试器触发下一步事件
   *
   * @author chitanda
   * @date 2022-11-30 18:11:04
   * @protected
   * @param {DebugMessage} response
   */
  protected stepEventHandle(response: DebugMessage): void {
    const { data } = response;
    const { location } = data;
    this._currentLine = location.lineNumber - 1;
    this.setGlobalVariables(data.fields);
    this.setVariables(data.variables);
  }

  /**
   * 将调试器返回的变量数据，处理入缓存
   *
   * @author chitanda
   * @date 2022-11-30 18:11:15
   * @protected
   * @param {StackFile} variables
   */
  protected setVariables(variables: StackFile): void {
    this.variables.clear();
    for (const key in variables) {
      if (Object.prototype.hasOwnProperty.call(variables, key)) {
        const data = variables[key];
        const v = new RuntimeVariable(data.name, data.value, data.type);
        this.variables.set(data.name, v);
      }
    }
  }

  /**
   * 将调试器返回的全局变量数据，处理入缓存
   *
   * @author chitanda
   * @date 2022-11-30 18:11:09
   * @protected
   * @param {StackFile} variables
   */
  protected setGlobalVariables(variables: StackFile): void {
    this.globalVariables.clear();
    for (const key in variables) {
      if (Object.prototype.hasOwnProperty.call(variables, key)) {
        const data = variables[key];
        const v = new RuntimeVariable(data.name, data.value, data.type);
        this.globalVariables.set(data.name, v);
      }
    }
  }

  /**
   * 启动远程调试
   *
   * @author chitanda
   * @date 2022-12-15 11:12:34
   * @param {string} system
   * @param {string} topic
   * @return {*}  {Promise<boolean>}
   */
  async start(system: string, topic: string): Promise<boolean> {
    this.ds.system = system;
    this.ds.topic = topic;
    const e = await this.ds.getEventTopic();
    e.on(this.topicHandle);
    return this.ds.openSession();
  }

  /**
   * 关闭远程调试
   *
   * @author chitanda
   * @date 2022-12-01 14:12:37
   * @return {*}  {Promise<boolean>}
   */
  async close(): Promise<boolean> {
    this.ds.system = '';
    this.ds.topic = '';
    const e = await this.ds.getEventTopic();
    e.off(this.topicHandle);
    await this.ds.unTopic();
    return this.ds.closeSession();
  }

  /**
   * 跳过当前，继续执行
   *
   * @author chitanda
   * @date 2022-11-28 11:11:38
   * @param {boolean} reverse
   */
  continue(_reverse: boolean): Promise<boolean> {
    return this.ds.run();
  }

  /**
   * 执行下一步
   *
   * @author chitanda
   * @date 2022-11-30 17:11:53
   * @param {boolean} _instruction
   * @param {boolean} _reverse
   * @return {*}  {Promise<boolean>}
   */
  step(_instruction: boolean, _reverse: boolean): Promise<boolean> {
    return this.ds.stepOver();
  }

  /**
   * 单步调试
   *
   * @author chitanda
   * @date 2022-11-30 17:11:56
   * @param {(number | undefined)} _targetId
   * @return {*}  {Promise<boolean>}
   */
  stepIn(_targetId: number | undefined): Promise<boolean> {
    return this.ds.stepInto();
  }

  /**
   * 单步跳出
   *
   * @author chitanda
   * @date 2022-11-30 17:11:03
   * @return {*}  {Promise<boolean>}
   */
  stepOut(): Promise<boolean> {
    return this.ds.stepOut();
  }

  getStepInTargets(_frameId: number): IRuntimeStepInTargets[] {
    // const line = this.getLine();
    // const words = this.getWords(this.currentLine, line);

    // return nothing if frameId is out of range
    // if (frameId < 0 || frameId >= words.length) {
    // 	return [];
    // }

    // const { name, index  }  = words[frameId];

    // make every character of the frame a potential "step in" target
    return 'name'.split('').map((c, ix) => {
      return {
        // id: index + ix,
        id: 100 + ix,
        label: `target: ${c}`,
      };
    });
  }

  /**
   * 返回堆栈信息
   *
   * @author chitanda
   * @date 2022-11-28 11:11:28
   * @param {number} startFrame
   * @param {number} endFrame
   * @return {*}  {IRuntimeStack}
   */
  stack(_startFrame: number, _endFrame: number): IRuntimeStack {
    const frames: IRuntimeStackFrame[] = [];

    const path = this._sourceFile?.fullPath || 'xxx';

    const uri = global.vfs.sys.getUri(path);

    frames.push({
      index: 0,
      name: `***`,
      file: `${uri.scheme}:${uri.path}`,
      line: this.currentLine,
      column: 0,
      instruction: 0,
    });

    return {
      frames,
      count: frames.length,
    };
  }

  /**
   * 获取所有断点
   *
   * @author chitanda
   * @date 2022-11-28 11:11:48
   * @param {string} path
   * @param {number} line
   * @return {*}  {number[]}
   */
  getBreakpoints(path: string, _line: number): number[] {
    if (this.breakPoints.has(path)) {
      return this.breakPoints.get(path)!.map(point => point.line);
    }
    return [];
  }

  /**
   * 设置断点
   *
   * @author chitanda
   * @date 2022-11-28 11:11:29
   * @param {string} path
   * @param {number} line
   * @return {*}  {Promise<IRuntimeBreakpoint>}
   */
  async setBreakPoint(path: string, line: number): Promise<IRuntimeBreakpoint> {
    await this.loadSource(path);

    const bp: IRuntimeBreakpoint = {
      verified: false,
      line,
      id: this.breakpointId++,
    };
    let bps = this.breakPoints.get(path);
    if (!bps) {
      bps = new Array<IRuntimeBreakpoint>();
      this.breakPoints.set(path, bps);
    }
    bps.push(bp);

    const pkg = this.getFullPkg();
    if (!pkg) {
      window.showErrorMessage(`未能识别 ${path}，包名称无法成功设置断点`);
    } else {
      const bol = await this.ds.addBreakpoint(`${pkg}:${line + 1}`);
      if (bol) {
        bp.verified = true;
      }
    }

    return bp;
  }

  /**
   * 清除指定文件指定行断点
   *
   * @author chitanda
   * @date 2022-11-28 11:11:37
   * @param {string} path
   * @param {number} line
   * @return {*}  {(IRuntimeBreakpoint | undefined)}
   */
  async clearBreakPoint(
    path: string,
    line: number,
  ): Promise<IRuntimeBreakpoint | undefined> {
    const pgk = await this.getFullPkg();
    await this.ds.deleteBreakpoint(`${pgk}:${line + 1}`);

    const bps = this.breakPoints.get(path);
    if (bps) {
      const index = bps.findIndex(bp => bp.line === line);
      if (index >= 0) {
        const bp = bps[index];
        bps.splice(index, 1);
        return bp;
      }
    }
    return undefined;
  }

  /**
   * 清除指定文件所有断点
   *
   * @author chitanda
   * @date 2022-11-28 11:11:54
   * @param {string} path
   */
  async clearBreakpoints(path: string): Promise<void> {
    this.breakPoints.delete(path);
    await this.ds.clearAllBreakpoint();
  }

  // clearAllDataBreakpoints(): void {
  //   console.log(`开始执行 clearAllDataBreakpoints`);
  //   this.breakAddresses.clear();
  // }

  // setExceptionsFilters(
  //   _namedException: string | undefined,
  //   _otherExceptions: boolean,
  // ): void {
  //   // this.namedException = namedException;
  //   // this.otherExceptions = otherExceptions;
  // }

  /**
   * 执行表达式
   *
   * @author chitanda
   * @date 2022-12-01 22:12:25
   * @param {string} expression
   * @return {*}  {(Promise<RuntimeVariable | undefined>)}
   */
  async evaluate(expression: string): Promise<RuntimeVariable | undefined> {
    const data = await this.ds.eval(expression);
    if (data) {
      return new RuntimeVariable(expression, data, 'string');
    }
  }

  /**
   * 获取对象属性
   *
   * @author chitanda
   * @date 2022-12-01 22:12:37
   * @param {string} expression
   * @return {*}  {(Promise<RuntimeVariable[]>)}
   */
  async listFields(expression: string): Promise<RuntimeVariable[]> {
    const data = await this.ds.listFields(expression);
    if (data) {
      const list: RuntimeVariable[] = [];
      for (const key in data) {
        if (Object.prototype.hasOwnProperty.call(data, key)) {
          const item = data[key];
          list.push(new RuntimeVariable(item.name, item.value, item.type));
        }
      }
      return list;
    }
    return [];
  }

  /**
   * 获取所有全局变量
   *
   * @author chitanda
   * @date 2022-11-30 19:11:36
   * @return {*}  {RuntimeVariable[]}
   */
  getGlobalVariables(): RuntimeVariable[] {
    return Array.from(this.globalVariables, ([_name, value]) => value);
  }

  /**
   * 获取指定全局变量
   *
   * @author chitanda
   * @date 2022-11-30 19:11:23
   * @param {string} name
   * @return {*}  {(RuntimeVariable | undefined)}
   */
  getGlobalVariable(name: string): RuntimeVariable | undefined {
    return this.globalVariables.get(name);
  }

  /**
   * 获取所有本地变量
   *
   * @author chitanda
   * @date 2022-11-30 17:11:52
   * @return {*}  {RuntimeVariable[]}
   */
  getLocalVariables(): RuntimeVariable[] {
    return Array.from(this.variables, ([_name, value]) => value);
  }

  /**
   * 获取本地变量
   *
   * @author chitanda
   * @date 2022-11-30 17:11:46
   * @param {string} name
   * @return {*}  {(RuntimeVariable | undefined)}
   */
  getLocalVariable(name: string): RuntimeVariable | undefined {
    return this.variables.get(name);
  }

  /**
   * 加载源文件
   *
   * @author chitanda
   * @date 2022-11-30 17:11:08
   * @private
   * @param {string} filePath
   * @return {*}  {Promise<void>}
   */
  private async loadSource(filePath: string): Promise<void> {
    if (this._sourceFile?.getUri().fsPath !== filePath) {
      this._sourceFile = await global.vfs.sys.getFile(
        global.vfs.sys.getUri(filePath),
      );
    }
  }

  /**
   * 获取当前打开文件的全类名,包含 包名 + 类名
   *
   * @author chitanda
   * @date 2022-12-15 10:12:13
   * @private
   * @return {*}  {string}
   */
  private getFullPkg(): string {
    if (this.sourceFile) {
      const { content } = this.sourceFile;
      if (content) {
        return content.data.rTObjectName;
      }
    }
    return '';
  }
}
